💡 AI 인사이트

🤖 AI가 여기에 결과를 출력합니다...

댓글 커뮤니티

쿠팡이벤트

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

검색

    로딩 중이에요... 🐣

    [코담] 웹개발·실전 프로젝트·AI까지, 파이썬·장고의 모든것을 담아낸 강의와 개발 노트

    08 FastAPI 연동 CRUD UI 만들기 frontend | ✅ 편저: 코담 운영자

    8강: HTML, Bootstrap, JavaScript로 FastAPI 연동 CRUD UI 만들기


    🔗 소스


    🎯 강의 목표

    • FastAPI 백엔드와 완전히 연동된 CRUD 인터페이스 구축
    • HTML + Bootstrap 기반 사용자 인터페이스 구현
    • JavaScript로 API 호출 (GET, POST, PUT, DELETE)

    🖼️ HTML 레이아웃 개요


    🧾 전체 HTML 코드 예시

    <!DOCTYPE html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>JS, FastAPI, SQLite로 만드는 CRUD</title>
        <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="container mt-5">
            <h1 class="text-center mb-4">JS, FastAPI, SQLite로 만드는 CRUD</h1>
    
            <div class="mb-3">
                <button class="btn btn-primary" data-toggle="modal" data-target="#userModal">새 사용자 추가</button>
            </div>
    
            <div id="errorLabel" class="alert alert-danger" style="display: none;"></div>
    
            <table id="userTableBody" class="table table-striped">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>이름</th>
                        <th>이메일</th>
                        <th>작업</th>
                    </tr>
                </thead>
                <tbody>
                  <!-- 여기에 서버에서 불러온 데이터로 테이블이 채워집니다 -->
                </tbody>
            </table>
    
            <div class="modal fade" id="userModal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
                <div class="modal-dialog">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="userModalLabel">사용자 추가 / 수정</h5>
                            <button type="button" class="close" data-dismiss="modal" aria-label="닫기">
                                <span aria-hidden="true">&times;</span>
                            </button>
                        </div>
                        <div class="modal-body">
                            <form id="userForm">
                                <div class="form-group">
                                    <label for="userName">이름</label>
                                    <input type="text" class="form-control" id="userName" required>
                                </div>
                                <div class="form-group">
                                    <label for="userEmail">이메일</label>
                                    <input type="email" class="form-control" id="userEmail" required>
                                </div>
                                <div id="errorMessage" class="alert alert-danger" style="display: none;"></div>
                            </form>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-secondary" data-dismiss="modal">닫기</button>
                            <button type="button" class="btn btn-primary" id="saveUserButton">저장</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    
        <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.2/dist/umd/popper.min.js"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
    </body>
    </html>
    

    🧩 주요 Bootstrap 클래스

    클래스명 역할
    container mt-5 여백 있는 메인 영역
    btn btn-primary 파란색 버튼
    table table-striped 줄무늬 스타일 테이블
    modal, modal-dialog 부트스트랩 모달 구성

    📋 구조 요약

    • 상단 제목: <h1>

    • 사용자 추가 버튼: #userModal 모달을 열기 위한 트리거

    • 사용자 테이블: #userTableBody에 데이터가 동적으로 추가됨

    • 사용자 입력 모달: 이름, 이메일 입력 후 저장 가능

    • 오류 메시지 영역: #errorLabel, #errorMessage

    🧩 주요 Bootstrap 클래스

    클래스명 역할
    container mt-5 여백 있는 메인 영역
    btn btn-primary 파란색 버튼
    table table-striped 줄무늬 스타일 테이블
    modal, modal-dialog 부트스트랩 모달 구성

    📋 구조 요약

    • 상단 제목: <h1>
    • 사용자 추가 버튼: #userModal 모달을 열기 위한 트리거
    • 사용자 테이블: #userTableBody에 데이터가 동적으로 추가됨
    • 사용자 입력 모달: 이름, 이메일 입력 후 저장 가능
    • 오류 메시지 영역: #errorLabel, #errorMessage

    🧩 주요 Bootstrap 클래스

    클래스명 역할
    container mt-5 여백 있는 메인 영역
    btn btn-primary 파란색 버튼
    table table-striped 줄무늬 스타일 테이블
    modal, modal-dialog 부트스트랩 모달 구성

    🧠 JavaScript 기능 전체 코드 및 설명

    1. 사용자 목록 조회 - fetchData()

    async function fetchData() {
        document.getElementById('errorLabel').style.display = 'none';
        try {
            const response = await fetch('http://localhost:8000/users');
            if (!response.ok) throw new Error('네트워크 오류');
    
            const data = await response.json();
            const tableBody = document.getElementById('userTableBody');
            tableBody.innerHTML = '';
    
            data.forEach(user => {
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td>${user.id}</td>
                    <td>${user.name}</td>
                    <td>${user.email}</td>
                    <td>
                        <button class="btn btn-warning btn-sm updateButton" data-id="${user.id}">수정</button>
                        <button class="btn btn-danger btn-sm deleteButton" data-id="${user.id}">삭제</button>
                    </td>
                `;
                tableBody.appendChild(row);
            });
        } catch (error) {
            document.getElementById('errorLabel').innerText = '사용자 정보를 불러오지 못했습니다. 나중에 다시 시도해주세요.';
            document.getElementById('errorLabel').style.display = 'block';
        }
    }
    
    • 서버로부터 사용자 목록을 가져와 테이블에 렌더링
    • 오류 발생 시 메시지를 화면에 출력

    2. 사용자 등록 및 수정 - addUser()

    async function addUser() {
        const name = document.getElementById('userName').value;
        const email = document.getElementById('userEmail').value;
        const userId = document.getElementById('saveUserButton').getAttribute('data-id');
    
        const newUser = { name: name, email: email };
        let url = 'http://localhost:8000/users';
        let method = 'POST';
    
        if (userId) {
            url += `/${userId}`;
            method = 'PUT';
        }
    
        try {
            const response = await fetch(url, {
                method: method,
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(newUser),
            });
    
            if (!response.ok) throw new Error('사용자 저장 실패');
    
            $('#userModal').modal('hide');
            document.getElementById('userForm').reset();
            fetchData();
        } catch (error) {
            document.getElementById('errorMessage').innerText = error;
            document.getElementById('errorMessage').style.display = 'block';
        } finally {
            document.getElementById('saveUserButton').removeAttribute('data-id');
        }
    }
    
    • userId 존재 시 수정, 없으면 신규 생성
    • 성공 시 모달 닫고 목록 갱신, 실패 시 오류 표시

    3. 사용자 정보 로딩 후 모달 열기 - openUpdateModal(userId)

    async function openUpdateModal(userId) {
        try {
            const response = await fetch(`http://localhost:8000/users/${userId}`);
            if (!response.ok) throw new Error('사용자 정보를 불러오지 못했습니다.');
    
            const user = await response.json();
            document.getElementById('userName').value = user.name;
            document.getElementById('userEmail').value = user.email;
            document.getElementById('saveUserButton').setAttribute('data-id', user.id);
            document.getElementById('userModalLabel').innerText = '사용자 수정';
            $('#userModal').modal('show');
        } catch (error) {
            document.getElementById('errorLabel').innerText = '사용자 정보를 불러오지 못했습니다. 다시 시도해주세요.';
            document.getElementById('errorLabel').style.display = 'block';
        }
    }
    
    • 수정 버튼 클릭 시 사용자 정보 조회 후 모달 열기

    4. 사용자 삭제 확인 및 실행 - deleteUser(userId)

    async function deleteUser(userId) {
        try {
            const response = await fetch(`http://localhost:8000/users/${userId}`, {
                method: 'DELETE',
            });
            if (!response.ok) throw new Error('삭제 실패');
            fetchData();
        } catch (error) {
            document.getElementById('errorLabel').innerText = '사용자 삭제 중 오류 발생. 다시 시도해주세요.';
            document.getElementById('errorLabel').style.display = 'block';
            setTimeout(() => {
                document.getElementById('errorLabel').style.display = 'none';
            }, 3000);
        }
    }
    
    • 사용자 삭제 후 테이블 목록 다시 불러옴

    5. 삭제 전 확인창 - confirmDelete(userId)

    function confirmDelete(userId) {
        if (confirm('정말로 이 사용자를 삭제하시겠습니까?')) {
            deleteUser(userId);
        }
    }
    
    • 실제 삭제 실행 전 사용자에게 확인

    6. 이벤트 바인딩 - DOMContentLoaded

    document.addEventListener("DOMContentLoaded", function() {
        document.getElementById('saveUserButton').addEventListener('click', addUser);
    
        document.getElementById('userTableBody').addEventListener('click', function(event) {
            if (event.target.classList.contains('updateButton')) {
                const userId = event.target.getAttribute('data-id');
                openUpdateModal(userId);
            }
            if (event.target.classList.contains('deleteButton')) {
                const userId = event.target.getAttribute('data-id');
                confirmDelete(userId);
            }
        });
    
        fetchData();
    });
    
    • 이벤트 초기화 및 첫 데이터 로딩 수행

    🔄 프론트와 FastAPI 연동 흐름

    📌 API 요청 정리

    프론트 동작 FastAPI URL 메서드
    사용자 전체 조회 /users GET
    사용자 단건 조회 /users/{id} GET
    사용자 생성 /users POST
    사용자 수정 /users/{id} PUT
    사용자 삭제 /users/{id} DELETE

    📌 FastAPI CORS 설정 필요

    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_methods=["*"],
        allow_headers=["*"],
    )
    
    • 브라우저의 CORS 정책으로 인해 백엔드에서 허용 필요

    ✅ 기능 검증 체크리스트

    • [x] 사용자 등록 → 테이블 자동 반영?
    • [x] 수정 → 기존 값 모달에 자동 입력?
    • [x] 삭제 → 즉시 테이블에서 제거?
    • [x] 서버 꺼졌을 때 오류 표시 동작?

    🧪 디버깅 팁


    🏁 마무리

    • FastAPI API와 100% 연동된 CRUD 인터페이스 구축
    • Bootstrap + JS만으로 관리 시스템 수준의 웹 UI 구현 가능
    • 이후 강의에서는 React 기반 SPA로 확장해보는 실습 예정

    📌 참고: 본 강의는 FastAPI 학습 시리즈 기반으로 제작되었습니다.

    TOP
    preload preload